package information;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JOptionPane;

/**
 * Job details all of a jobs information. 
 * 
 * @author Natalie Harris
 * @author Christian (isOverlappingDateRange() & getPositionsFilled(final int theLevel))
 *
 */
public class Job {
	
	/**
	 * Index for light work. 
	 */
	public static final int LIGHT = 0;
	
	/**
	 * Index for medium work. 
	 */
	public static final int MEDIUM = 1;
	
	/**
	 * Index for heavy work.
	 */
	public static final int HEAVY = 2;

	private User myParkManager;
	
	/**
	 * A map containing all the Volunteers for this Job as the keys and the value they are connected to is their level. 0 = light 1 = medium 2 = heavy 
	 */
	private Map<User, Integer> myVolunteers;
	
	/**
	 * A list containing the description of each level for this Job.  0 = light 1 = medium 2 = heavy
	 */
	private List<String> myLevelDetails;
	
	/**
	 * A list containing the number of volunteers needed for each level of this job. 0 = light 1 = medium 2 = heavy
	 */
	private List<Integer> myLevelNumbers;
	
	private Date myStartDate;
	
	private Date myEndDate;
	
	private String myDescription;
	
	private boolean myWeekday;
	
	private boolean myHoliday;
	
	private Park myPark;
	
	
	/**
	 * Creates a new Job. 
	 * 
	 * @param theManager The park manager. 
	 * @param theLevelDetails The details of each level. 0 = light, 1 = medium, 2 = heavy --> Number of Volunteers --> Level Description
	 * @param theStartDate The starting date. 
	 * @param theEndDate The ending date. 
	 * @param theDescription The job description. 
	 */
	//@requires all parameters must not be null
	//@ensures all fields will be initialized
	public Job(final User theManager, final List<String> theLevelDetails, final List<Integer> theLevelNumbers, 
			final Date theStartDate, final Date theEndDate, final String theDescription, final boolean theWeekday, final boolean theHoliday, final Park thePark) {
		myParkManager = theManager;
		myVolunteers = new HashMap<User, Integer>();
		myLevelDetails = theLevelDetails;
		myLevelNumbers = theLevelNumbers;
		myStartDate = theStartDate;
		myEndDate = theEndDate;
		myDescription = theDescription;
		myWeekday = theWeekday;
		myHoliday = theHoliday;
		myPark = thePark;
	}
	
	
	/**
	 * Adds a Volunteer to this Job. 
	 * 
	 * @param theVolunteer The volunteer to be added to the Job. 
	 * @param theLevel The level the volunteer has committed to work. 0 = light 1 = medium 2 = heavy
	 */
	//@requires the volunteer is valid to be added to the job at this level. 
	public void addVolunteer(final User theVolunteer, final Integer theLevel) {
		myVolunteers.put(theVolunteer, theLevel);
	}
	
	/**
	 * Returns the list of Volunteers signed up for this job in a set. 
	 * 
	 * @return A list of Volunteers signed up for this Job. 
	 */
	public Set<User> getVolunteers() {
		return myVolunteers.keySet();
	}
	
	/**
	 * Returns the park manager as a user. 
	 * 
	 * @return Returns the park manager. 
	 */
	public User getManager() {
		return myParkManager;
	}
	
	/**
	 * Returns the level of the passed in Volunteer. 0 for light, 1 for medium and 2 for heavy. 
	 */
	//@requires the user passed in is signed up for this job. 
	//@ensures return == 0 || return == 1 || return == 2
	public int getVolunteerLevel(final User theVolunteer) {
		return myVolunteers.get(theVolunteer);
	}
	
	/**
	 * Searches for a certain volunteer being signed up for this job. 
	 * 
	 * @param theVolunteer The volunteer being search for. 
	 * @return True if the volunteer is signed up for this job, false is the volunteer is not signed up for this job. 
	 */
	//@requires theVolunteer is a User of type Volunteer
	public boolean searchForVols(final User theVolunteer) {
		return myVolunteers.containsKey(theVolunteer);
	}
	
	/**
	 * Returns true or false based on whether the park manager passed in is the park manager for this job. 
	 * 
	 * @param theParkManager The park manager being searched for. 
	 * @return True if the park manager passed in is the park manager of this job, false if they are not. 
	 */
	//@requires theParkManager is a User of type ParkManager
	public boolean searchForPM(final User theParkManager) {
		return theParkManager.equals(myParkManager);
	}
	
	/**
	 * Returns true if the volunteer is able to be added to this job at the passed in level. 
	 * 
	 * @param theVolunteer The volunteer being validated. 
	 * @param theLevel The level the volunteer wants to be added to. 
	 * @return True if the volunteer can be added to the job at the level, false otherwise. 
	 */
	//@requires theVolunteer must be a User of type Volunteer
	//@requires theLevel == 0 || theLevel == 1 || theLevel == 2
	public boolean validateVolunteer(final User theVolunteer, final Integer theLevel) {
		boolean valid = true;
		if(theVolunteer.isBlackballed()) {
			valid = false;
			JOptionPane.showMessageDialog(null, "You are not allowed to sign up for jobs at this time. Please contact the office" + 
					"if you have any questions.");
		}
		int levelCount = 0;
		for(Integer level : myVolunteers.values()){
			if(level == theLevel){
				levelCount++;
			}
		}
		if(levelCount == myLevelNumbers.get(theLevel)) {
			valid = false;
			JOptionPane.showMessageDialog(null, "There are already the required number of volunteers signed up for this level! " + 
					"Sign up for a different level or find another job. ");
		}
		if(!myStartDate.isFuture()) {
			valid = false;
			JOptionPane.showMessageDialog(null, "This job has already passed. Please sign up for a job in the future. ");
		}
		return valid;	
	}
	
	/**
	 * Returns the starting date of a job. 
	 * 
	 * @return Start date of job. 
	 */
	public Date getStartDate() {
		return myStartDate;
	}
	
	/**
	 * Returns the ending date of the job. 
	 * 
	 * @return End date of job. 
	 */
	public Date getEndDate() {
		return myEndDate;
	}
	
	/**
	 * Returns the description of light work for this job. 
	 * 
	 * @return The description of light work. 
	 */
	public String getLightDetials() {
		return myLevelDetails.get(LIGHT);
	}
	
	/**
	 * Returns the description of medium work for this job. 
	 * 
	 * @return The description of medium work. 
	 */
	public String getMediumDetials() {
		return myLevelDetails.get(MEDIUM);
	}
	
	/**
	 * Returns the description of heavy work for this job. 
	 * 
	 * @return The description of heavy work. 
	 */
	public String getHeavyDetails() {
		return myLevelDetails.get(HEAVY);
	}
	
	/**
	 * Returns the description of this job. 
	 * 
	 * @return The description of this job. 
	 */
	public String getDescription() {
		return myDescription;
	}
	
	public boolean isHoliday() {
		return myHoliday;
	}
	
	public boolean isWeekday() {
		return myWeekday;
	}
	
	public Park getPark() {
		return myPark;
	}
	
	//@ensures return >= 0
	public Integer getLightNumber() {
		return myLevelNumbers.get(0);
	}
	
	//@ensures return >= 0
	public Integer getMediumNumber() {
		return myLevelNumbers.get(1);
	}
	
	//@ensures return >= 0
	public Integer getHeavyNumber() {
		return myLevelNumbers.get(2);
	}
	
	/**
	 * Returns the number of spots currently filled by volunteers for the passed in level. 
	 * 
	 * @author Christian
	 * @param theLevel 0 for light, 1 for medium, 2 for heavy. 
	 * @return the number of spots filled for the level. 
	 */
	//@requires theLevel == 0 || theLevel == 1 || theLevel == 2
	//@ensures return >= 0
	public Integer getPositionsFilled(final int theLevel) {
		int filled = 0;
		for (User user : myVolunteers.keySet()) {
			if (myVolunteers.get(user).equals(theLevel)) {
				filled++;
			}
		}
		return filled;
	}
	
	/**
	   * Returns true if the two objects are equal. Returns false
	   * if the two objects are not equal. Two objects are equal if they have
	   * the same values in all of their fields. 
	   * 
	   * @param the_other An object to be compared. 
	   * @return True or False of whether the two objects are equal  
	   */
	//@requires theOther != null
	public boolean equals(final Object theOther) {
		boolean isEqual = false;
		if (theOther == this ) isEqual = true;
		else if (theOther instanceof Job) {
			final Job theOtherCast = (Job) theOther;
			isEqual = myParkManager.equals(theOtherCast.myParkManager) && myStartDate.equals(theOtherCast.myStartDate) &&
					myEndDate.equals(theOtherCast.myEndDate) && myVolunteers.equals(theOtherCast.myVolunteers) &&
					myLevelDetails.equals(theOtherCast.myLevelDetails) && myLevelNumbers.equals(theOtherCast.myLevelNumbers) &&
					myDescription.equals(theOtherCast.myDescription);
		}
		return isEqual;
	}
	
	/**
	 * Checks to see if two job date ranges are overlapping.
	 * This method will return true if they are overlapping on exact dates vs next dates
	 * i.e. if start date for job1 is on THE SAME DAY as end date for job2
	 * @author Christian
	 * @param theOtherJob
	 * @return true if jobs are overlapping, false if they are not.
	 */
	//@requires start date and end dates must be properly instantiated
	public boolean isOverlappingDateRange(final Job theOtherJob) {
		//other job's start and end numbers
		long otherStartNum = theOtherJob.getStartDate().dateToNumber();
		long otherEndNum = theOtherJob.getEndDate().dateToNumber();
		//this job's start and end numbers
		long startNum = myStartDate.dateToNumber();
		long endNum = myEndDate.dateToNumber();
		
		return (otherStartNum <= endNum) && (otherEndNum >= startNum);
	}
}
